package com.zhangh.summer.admin.support.id.impl;

import com.zhangh.summer.admin.support.ds.common.MixedDS;
import com.zhangh.summer.admin.support.exception.BizException;
import com.zhangh.summer.admin.support.id.IdGen;
import com.zhangh.summer.admin.support.id.IdSetting;
import com.zhangh.summer.admin.support.utils.json.JsonEncoder;
import com.zhangh.summer.admin.support.lock.RedisLocker;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @description: TODO
 * @author: zhanghui  zhanghlucky@163.com
 * @create: 2021-09-28 21:30
 **/
@Component
@Slf4j
public class IdGenImpl implements IdGen, IdSetting {
    private static final String ID_WORKER="ID_WORKER";
    private static final String WORKER_LOCK="WORKER_LOCK";
    private static final List<Long> WORKER_LIST=new ArrayList<>();

    @Resource
    private MixedDS mixedDS;
    private static  final long HEART_TIME=30*1000;
    private static  final long NODE_TIMEOUT=40*1000;

    private Long workerID;
    private long dataCenterId;
    private long sequence;
    private long tStart = 1585644268888L;
    private long workerIdBits = 5L;
    private long dataCenterIdBits = 0L;//设置机房位为0
    private long sequenceBits = 8L;//12
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);

    private long workerIdShift = sequenceBits;
    private long dataCenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
    private long sequenceMask = -1L ^ (-1L << sequenceBits);
    private long lastTimestamp = -1L;


    @Override
    public synchronized  Long getID() {
        if (null==workerID){
           log.error("等待分配workerID，请稍后重试");
           throw BizException.exception("等待分配workerID，请稍后重试");
        }
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            log.error("clock is moving backwards. Rejecting requests until {}",lastTimestamp);
            throw  BizException.exception("clock is moving backwards. Rejecting requests until {0}",lastTimestamp);
        }
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }
        lastTimestamp = timestamp;
        return ((timestamp - tStart) << timestampLeftShift) |
                (dataCenterId << dataCenterIdShift) |
                (workerID << workerIdShift) | sequence;

    }

    /**
     * 当某一毫秒的时间，产生的id数 超过4095，系统会进入等待，直到下一毫秒，系统继续产生ID
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();

        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
    private long timeGen(){
        return System.currentTimeMillis();
    }

    /**
     * 初始化workerID
     */
    public void initWorkerID(){
        log.info("分配id发号器 workerID");
        //加发号锁
        RedisLocker.getInstance().lock(WORKER_LOCK);
        //获取在线节点
        Map<String,String> map=this.mixedDS.redis().hgetAll(ID_WORKER);
        List<WorkerBO> workerBOList=map.values().stream().map(json->  {
            WorkerBO bo= JsonEncoder.DEFAULT.decode(json,WorkerBO.class);
            return bo;
        }).collect(Collectors.toList());
        long currentTime=timeGen();
        List<WorkerBO> aliveWorkerBOList=workerBOList.stream().filter(item->(currentTime-item.getHeartTime())<NODE_TIMEOUT).collect(Collectors.toList());
        List<WorkerBO> offlineList=workerBOList.stream().filter(item->(currentTime-item.getHeartTime())>=NODE_TIMEOUT).collect(Collectors.toList());
        for (WorkerBO workerBO : offlineList) {
            this.mixedDS.redis().hdel(ID_WORKER,workerBO.getWorkerID()+"");//删除超时的节点
            log.info("删除离线节点 workerID {}",workerBO.getWorkerID());
        }
        //可选择的workerID
        List<Long> workerIDList = WORKER_LIST.stream().filter((wkID ->
                aliveWorkerBOList.stream().map(WorkerBO::getWorkerID)
                        .noneMatch(id -> wkID.equals(id)))).collect(Collectors.toList());
        if (workerIDList.size()==0){
            log.error("clientID超过31个");
            throw BizException.exception("clientID超过31个");
        }
        String id=workerIDList.get(0).toString();//取第一个作为workerID
        WorkerBO workerBO = new WorkerBO();
        workerBO.setHeartTime(timeGen());
        workerBO.setWorkerID(Long.valueOf(id));
        this.mixedDS.redis().hset(ID_WORKER,id,JsonEncoder.DEFAULT.encode(workerBO));//记录workerID

        workerID=workerBO.getWorkerID();
        log.info("获取workerID:{}",workerID);
        new WorkerHeartThread().start();//启动守护线程维持心跳
        RedisLocker.getInstance().unlock(WORKER_LOCK);
    }
    @PostConstruct
    public void init(){
        for (int i = 1; i < 32; i++) {
            WORKER_LIST.add((long)i);
        }
    }

    @Data
    static class WorkerBO{
        private Long workerID;
        private Long heartTime;
    }
    //心跳维持线程
    class WorkerHeartThread extends Thread {
        public WorkerHeartThread() {
            this.setDaemon(true); //设置成为守护线程
        }
        public void run() {
            try {
                Thread.sleep(HEART_TIME);
                log.debug("维持workerID  {} 心跳",workerID);
                WorkerBO workerBO = new WorkerBO();
                workerBO.setHeartTime(timeGen());
                workerBO.setWorkerID(workerID);
                mixedDS.redis().hset(ID_WORKER, workerID + "", JsonEncoder.DEFAULT.encode(workerBO));
                //维持心跳
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
